package com.example.aboutwork.nonblocking;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.example.aboutwork.nonblocking.future.Future;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public interface FutureFunc<A> extends Function<ExecutorService, Future<A>> {

    Logger log = LoggerFactory.getLogger(FutureFunc.class);

    static <A> A run(ExecutorService es, FutureFunc<A> func) throws InterruptedException {
        AtomicReference<A> reference = new AtomicReference<>();
        CountDownLatch latch = new CountDownLatch(1);
        Future<A> future = func.apply(es);
        future.apply(a -> {
            reference.set(a);
            latch.countDown();
            return null;
        });
        latch.await();
        return reference.get();
    }

    static <A> FutureFunc<A> unit(A a) {
        return es -> new Future<A>() {
            @Override
            public Void apply(Function<A, Void> f) {
                return f.apply(a);
            }
        };
    }

    static <A> FutureFunc<A> delay(A a) {
        return es -> new Future<A>() {
            @Override
            public Void apply(Function<A, Void> f) {
                return f.apply(a);
            }
        };
    }

    static Void eval(ExecutorService es, Supplier<Void> supplier) {
        try {
            return es.submit(supplier::get).get();
        } catch (Exception e) {
            log.error("eval求值出错", e);
            throw new RuntimeException(e);
        }
    }

    static <A> FutureFunc<A> fork(Supplier<FutureFunc<A>> lazyFuncA) {
        return es -> new Future<A>() {
            @Override
            public Void apply(Function<A, Void> f) {
                return eval(es, () -> {
                    Future<A> aFuture = lazyFuncA.get().apply(es);
                    return aFuture.apply(f);
                });
            }
        };
    }


    static <A> FutureFunc<A> async(Future<A> fa) {
        return es -> new Future<A>() {
            @Override
            public Void apply(Function<A, Void> g) {
                return fa.apply(g);
            }
        };
    }


    static <A, B, C> FutureFunc<C> map2(FutureFunc<A> funcA,
                                         FutureFunc<B> funcB,
                                         BiFunction<A, B, C> f) {
        return es -> new Future<C>() {
            @Override
            public Void apply(Function<C, Void> cVoidFunction) {
                try {
                    FutureFunc<A> forkA = fork(() -> funcA);
                    FutureFunc<B> forkB = fork(() -> funcB);
                    A a = run(es, forkA);
                    B b = run(es, forkB);
                    C c = f.apply(a, b);
                    return cVoidFunction.apply(c);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        };
    }

    static <A, B> FutureFunc<B> map(FutureFunc<A> funcA, Function<A, B> f) {
        return es -> new Future<B>() {
            @Override
            public Void apply(Function<B, Void> bVoidFunction) {
                Future<A> aFuture = funcA.apply(es);
                Function<A, Void> aVoidFunc1 = (A a) -> eval(es, () -> bVoidFunction.apply(f.apply(a)));
                return aFuture.apply(aVoidFunc1);
            }
        };
    }


    static <A, B> FutureFunc<B> flatMap(FutureFunc<A> funcA, Function<A, FutureFunc<B>> f) {
        return es -> new Future<B>() {
            @Override
            public Void apply(Function<B, Void> bFunc) {
                return funcA.apply(es).apply(a -> f.apply(a).apply(es).apply(bFunc));
            }
        };
    }


    static <A> FutureFunc<A> choice(FutureFunc<Boolean> booleanFunc,
                                     FutureFunc<A> choiceFunc,
                                     FutureFunc<A> notChoiceFunc) {
        return es -> new Future<A>() {
            @Override
            public Void apply(Function<A, Void> funcA) {
                return booleanFunc.apply(es).apply(b -> {
                    if (b) {
                        return eval(es, () -> choiceFunc.apply(es).apply(funcA));
                    } else {
                        return eval(es, () -> notChoiceFunc.apply(es).apply(funcA));
                    }
                });

            }
        };
    }

    static <A> FutureFunc<A> lazyUnit(Supplier<A> aSupplier) {
        return fork(() -> unit(aSupplier.get()));
    }

    static <A, B> Function<A, FutureFunc<B>> asyncF(Function<A, B> f) {
        return a -> new FutureFunc<B>() {
            @Override
            public Future<B> apply(ExecutorService es) {
                return lazyUnit(() -> f.apply(a)).apply(es);
            }
        };
    }


    static <A> FutureFunc<List<A>> sequenceRight(List<FutureFunc<A>> as) {
        if (Objects.isNull(as))
            return unit(null);
        FutureFunc<A> first = as.get(0);
        List<FutureFunc<A>> remains = as.subList(1, as.size());
        return map2(first, fork(() -> sequenceBalance(remains)), (a, as1) -> {
            List<A> result = Lists.newArrayList(as1);
            result.add(a);
            return result;
        });
    }

    static <A> FutureFunc<List<A>> sequence(List<FutureFunc<A>> as) {
        return sequenceBalance(as);
    }


    static <A> FutureFunc<List<A>> sequenceBalance(List<FutureFunc<A>> as) {

        if (CollectionUtils.isEmpty(as)) {
            return unit(Lists.newArrayList());
        }

        if (as.size() == 1) {
            return map(as.get(0), Lists::newArrayList);
        }

        List<FutureFunc<A>> leftList = as.subList(0, as.size() / 2);
        List<FutureFunc<A>> rightList = as.subList(as.size() / 2, as.size());

        return map2(sequenceBalance(leftList), sequenceBalance(rightList), (as1, as2) -> {
            List<A> list = Lists.newArrayList(as1);
            list.addAll(as2);
            return list;
        });

    }


    static <A, B> FutureFunc<List<B>> parMap(List<A> as, Function<A, B> f) {
        List<FutureFunc<B>> bFunctions = as.stream().map(a -> asyncF(f).apply(a)).collect(Collectors.toList());
        return sequence(bFunctions);
    }

    static <A, B> FutureFunc<List<B>> parMapBalance(List<A> as, Function<A, B> f) {
        List<FutureFunc<B>> bFunctions = as.stream().map(a -> asyncF(f).apply(a)).collect(Collectors.toList());
        return sequenceBalance(bFunctions);
    }

}
